/**
 * Copyright 2011 Adrian Witas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.abstractmeta.commandify.generator.builder.handler;

import com.google.common.base.Strings;
import org.abstractmeta.code.g.code.*;
import org.abstractmeta.code.g.code.handler.MethodHandler;
import org.abstractmeta.code.g.core.annotation.SuppressWarningsBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaConstructorBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaMethodBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaTypeBuilderImpl;
import org.abstractmeta.code.g.core.generator.ClassGeneratorConfig;
import org.abstractmeta.code.g.core.internal.ParameterizedTypeImpl;
import org.abstractmeta.code.g.core.util.CodeGeneratorUtil;
import org.abstractmeta.code.g.core.util.JavaTypeUtil;
import org.abstractmeta.code.g.core.util.ReflectUtil;
import org.abstractmeta.code.g.generator.CodeGenerator;
import org.abstractmeta.code.g.generator.Context;
import org.abstractmeta.commandify.Commandify;
import org.abstractmeta.commandify.command.Commands;
import org.abstractmeta.commandify.core.command.AbstractFieldCommand;
import org.abstractmeta.commandify.core.command.CommandifyCollection;
import org.abstractmeta.commandify.core.command.CommandifyMap;
import org.abstractmeta.commandify.core.command.collection.CommandifyList;
import org.abstractmeta.commandify.core.command.collection.CommandifyNavigableSet;
import org.abstractmeta.commandify.core.command.collection.CommandifySet;
import org.abstractmeta.commandify.core.command.collection.CommandifySortedSet;
import org.abstractmeta.commandify.core.command.map.CommandifyNavigableMap;
import org.abstractmeta.commandify.core.command.map.CommandifySortedMap;
import org.abstractmeta.commandify.core.command.reflectify.ReflectifyFieldCommand;
import org.abstractmeta.commandify.core.command.reflectify.ReflectifyPrimitiveFieldCommand;
import org.abstractmeta.commandify.core.command.util.CommandifyUtil;
import org.abstractmeta.commandify.generator.util.CommandifyGeneratorUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Represents MethodCommandify
 * <p>
 * </p>
 *
 * @author Adrian Witas
 */
public class MethodCommandify implements MethodHandler {

    @Override
    public void handle(JavaTypeBuilder owner, JavaMethod target, Context context) {
        if (target.getName().startsWith("get")) return;


        if (CodeGeneratorUtil.isSetterMethod(target.getName())) {
            buildFieldSetterCommand(owner, target, context);
        }

    }


    private void buildFieldSetterCommand(JavaTypeBuilder owner, JavaMethod target, Context context) {
        boolean hasBody = !target.getBodyLines().isEmpty();

        if(hasBody) {
            target.getBodyLines().clear();
        }

        Type superType = CommandifyGeneratorUtil.getSuperType(owner);
        JavaParameter theFirstParameter = target.getParameters().get(0);
        String fieldName = CodeGeneratorUtil.extractFieldNameFromMethodName(target.getName());
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());
        Class rawType = ReflectUtil.getRawClass(fieldType);
        boolean isFieldCollectionType = Collection.class.isAssignableFrom(rawType);
        if (isFieldCollectionType) {
            buildCollectionSetter(owner, target, context, fieldName, theFirstParameter, superType);
            return;
        }
        boolean isFieldMapType = Map.class.isAssignableFrom(rawType);
        if (isFieldMapType) {
            buildMapSetter(owner, target, context, fieldName, theFirstParameter, superType);
            return;

        }
        if (rawType.isPrimitive() || "java.lang".equals(rawType.getPackage().getName())) {
            buildPrimitiveSetter(owner, target, context, fieldName, theFirstParameter, superType);
            return;

        }
        buildFieldSetter(owner, target, context, fieldName, theFirstParameter, superType);
    }

    protected void buildFieldSetter(JavaTypeBuilder owner, JavaMethod target, Context context, String fieldName, JavaParameter theFirstParameter, Type superType) {
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());
        StringBuilder methodBuilder = new StringBuilder();
        Type reflectifyCommand = new ParameterizedTypeImpl(null, ReflectifyFieldCommand.class, superType, theFirstParameter.getType());
        String fieldAccessor = CodeGeneratorUtil.getAccessorCode(superType, fieldName, fieldType);
        methodBuilder.append(String.format("getCommands().add(\n\tnew %s(getCommands().getReflectify(), \"%s\", %s, %s)\n);\n",
                owner.getImporter().getSimpleTypeName(reflectifyCommand),
                fieldName,
                theFirstParameter.getName(),
                fieldAccessor
        ));
        methodBuilder.append(String.format(CodeGeneratorUtil.getMutatorPattern(superType, fieldName, fieldType) + ";", theFirstParameter.getName()));
        target.getBodyLines().add(methodBuilder.toString());

    }


    protected void buildPrimitiveSetter(JavaTypeBuilder owner, JavaMethod target, Context context, String fieldName, JavaParameter theFirstParameter, Type superType) {
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());
        StringBuilder methodBuilder = new StringBuilder();

        Type objectType = ReflectUtil.getObjectType(theFirstParameter.getType());
        Type reflectifyCommand = new ParameterizedTypeImpl(null, ReflectifyPrimitiveFieldCommand.class, superType, objectType);
        String fieldAccessor = CodeGeneratorUtil.getAccessorCode(superType, fieldName, fieldType);
        methodBuilder.append(String.format("getCommands().add(\n\tnew %s(getCommands().getReflectify(), \"%s\", %s, %s)\n);\n",
                owner.getImporter().getSimpleTypeName(reflectifyCommand),
                fieldName,
                theFirstParameter.getName(),
                fieldAccessor
        ));
        methodBuilder.append("System.out.println(getCommands().size());");
        methodBuilder.append(String.format(CodeGeneratorUtil.getMutatorPattern(superType, fieldName, fieldType) + ";", theFirstParameter.getName()));
        target.getBodyLines().add(methodBuilder.toString());
    }


    protected void buildMapSetter(JavaTypeBuilder owner, JavaMethod target, Context context, String fieldName, JavaParameter theFirstParameter, Type superType) {
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());
        Class rawType = ReflectUtil.getRawClass(fieldType);
        Type[] argumentTypes = ReflectUtil.getGenericActualTypeArguments(theFirstParameter.getType());
        Type mapType;

        if (NavigableMap.class.isAssignableFrom(rawType)) {
            mapType = new ParameterizedTypeImpl(null, CommandifyNavigableMap.class, argumentTypes);
        } else if (SortedMap.class.isAssignableFrom(rawType)) {
            mapType = new ParameterizedTypeImpl(null, CommandifySortedMap.class, argumentTypes);
        } else {
            mapType = new ParameterizedTypeImpl(null, CommandifyMap.class, argumentTypes);
        }
        buildRepeatableType(owner, target, context, fieldName, theFirstParameter, superType, mapType);
    }

    protected void buildCollectionSetter(JavaTypeBuilder owner, JavaMethod target, Context context, String fieldName, JavaParameter theFirstParameter, Type superType) {
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());
        Class rawType = ReflectUtil.getRawClass(fieldType);
        Type[] argumentTypes = ReflectUtil.getGenericActualTypeArguments(theFirstParameter.getType());
        Type collectionType;

        if (NavigableSet.class.isAssignableFrom(rawType)) {
            collectionType = new ParameterizedTypeImpl(null, CommandifyNavigableSet.class, argumentTypes);
        } else if (SortedSet.class.isAssignableFrom(rawType)) {
            collectionType = new ParameterizedTypeImpl(null, CommandifySortedSet.class, argumentTypes);
        } else if (Set.class.isAssignableFrom(rawType)) {
            collectionType = new ParameterizedTypeImpl(null, CommandifySet.class, argumentTypes);
        } else if (List.class.isAssignableFrom(rawType)) {
            collectionType = new ParameterizedTypeImpl(null, CommandifySet.class, argumentTypes);
        } else {
            collectionType = new ParameterizedTypeImpl(null, CommandifyCollection.class, argumentTypes);

        }
        buildRepeatableType(owner, target, context, fieldName, theFirstParameter, superType, collectionType);
    }

    protected void buildRepeatableType(JavaTypeBuilder owner, JavaMethod target, Context context, String fieldName, JavaParameter theFirstParameter, Type superType, Type commandifyFieldType) {
        Type fieldType = ReflectUtil.getObjectType(theFirstParameter.getType());

        String commandifyTypeName = owner.getImporter().getSimpleTypeName(commandifyFieldType);
        StringBuilder methodBuilder = new StringBuilder();
        methodBuilder.append(String.format("%s = new %s(%s);\n", theFirstParameter.getName(), commandifyTypeName, theFirstParameter.getName()));
        Type reflectifyCommand = new ParameterizedTypeImpl(null, ReflectifyFieldCommand.class, superType, theFirstParameter.getType());


        String fieldAccessor = CodeGeneratorUtil.getAccessorCode(superType, fieldName, fieldType);
        methodBuilder.append(String.format("getCommands().add(\n\tnew %s(getCommands().getReflectify(), \"%s\", %s, %s)\n);\n",
                owner.getImporter().getSimpleTypeName(reflectifyCommand),
                fieldName,
                theFirstParameter.getName(),
                fieldAccessor
        ));


        methodBuilder.append(String.format("CommandifyUtil.associateAccessor(%s, this, \"%s\");\n", theFirstParameter.getName(), fieldName));
        methodBuilder.append(String.format(CodeGeneratorUtil.getMutatorPattern(superType, fieldName, fieldType) + ";", theFirstParameter.getName()));
        owner.getImporter().addTypes(CommandifyUtil.class);
        target.getBodyLines().add(methodBuilder.toString());

    }




}
